/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.auth;

import com.je.auth.check.fun.AuthRetFunction;
import com.je.auth.listener.AuthEvent;
import com.je.auth.check.util.FoxUtil;
import com.je.common.auth.token.AuthSession;
import com.je.common.auth.token.TokenSign;

import java.util.Map;
import java.util.Set;

/**
 * session操作类
 */
public class AuthSessionTemplate {

    private AuthEngine authEngine;

    public AuthEngine getAuthEngine() {
        return authEngine;
    }

    public void setAuthEngine(AuthEngine authEngine) {
        this.authEngine = authEngine;
    }

    public AuthSession createSession(String sessionId) {
        AuthSession authSession = new AuthSession(sessionId);
        AuthEvent createSessionEvent = new AuthEvent();
        createSessionEvent.setEventType(AuthEvent.AuthEventType.CREATE_SESSION);
        createSessionEvent.setSessionId(sessionId);
        authEngine.trigger(createSessionEvent);
        return authSession;
    }

    /**
     * 更新Session（从持久库更新刷新一下）
     */
    public void update(AuthSession session) {
        authEngine.getAuthTokenDao().updateSession(session);
    }

    /**
     * 添加一个token签名
     *
     * @param tokenSign token签名
     */
    public void addTokenSign(AuthSession authSession, TokenSign tokenSign) {
        // 如果已经存在于列表中，则无需再次添加
        for (TokenSign tokenSign2 : authSession.getTokenSignList()) {
            if (tokenSign2.getValue().equals(tokenSign.getValue())) {
                return;
            }
        }
        // 添加并更新
        authSession.addTokenSign(tokenSign);
        update(authSession);
    }

    /**
     * 添加一个token签名
     *
     * @param tokenValue token值
     * @param device     设备标识
     */
    public void addTokenSign(AuthSession authSession, String tokenValue, String device) {
        addTokenSign(authSession, new TokenSign(tokenValue, device));
    }

    /**
     * 移除一个token签名
     *
     * @param tokenValue token名称
     */
    public void removeTokenSign(AuthSession authSession, String tokenValue) {
        TokenSign tokenSign = authSession.getTokenSign(tokenValue);
        if (authSession.removeTokenSign(tokenSign)) {
            update(authSession);
        }
    }

    /**
     * 注销Session (从持久库删除)
     */
    public void logout(String sessionId) {
        authEngine.getAuthTokenDao().deleteSession(sessionId);
        // $$ 通知监听器
        AuthEvent authEvent = new AuthEvent();
        authEvent.setEventType(AuthEvent.AuthEventType.LOGOUT_SESSION);
        authEvent.setSessionId(sessionId);
        authEngine.trigger(authEvent);
    }

    // ----------------------- 一些操作

    /**
     * 当Session上的tokenSign数量为零时，注销会话
     */
    public void logoutByTokenSignCountToZero(AuthSession authSession) {
        if (authSession.getTokenSignList().size() == 0) {
            logout(authSession.getId());
        }
    }

    /**
     * 获取此Session的剩余存活时间 (单位: 秒)
     *
     * @return 此Session的剩余存活时间 (单位: 秒)
     */
    public long getTimeout(String sessionId) {
        return authEngine.getAuthTokenDao().getSessionTimeout(sessionId);
    }

    /**
     * 修改此Session的剩余存活时间
     *
     * @param timeout 过期时间 (单位: 秒)
     */
    public void updateTimeout(String sessionId, long timeout) {
        authEngine.getAuthTokenDao().updateSessionTimeout(sessionId, timeout);
    }

    /**
     * 修改此Session的最小剩余存活时间 (只有在Session的过期时间低于指定的minTimeout时才会进行修改)
     *
     * @param minTimeout 过期时间 (单位: 秒)
     */
    public void updateMinTimeout(String sessionId, long minTimeout) {
        long min = trans(minTimeout);
        long curr = trans(getTimeout(sessionId));
        if (curr < min) {
            updateTimeout(sessionId, minTimeout);
        }
    }

    /**
     * 修改此Session的最大剩余存活时间 (只有在Session的过期时间高于指定的maxTimeout时才会进行修改)
     *
     * @param maxTimeout 过期时间 (单位: 秒)
     */
    public void updateMaxTimeout(String sessionId, long maxTimeout) {
        long max = trans(maxTimeout);
        long curr = trans(getTimeout(sessionId));
        if (curr > max) {
            updateTimeout(sessionId, maxTimeout);
        }
    }

    /**
     * value为 -1 时返回 Long.MAX_VALUE，否则原样返回
     *
     * @param value /
     * @return /
     */
    protected long trans(long value) {
        return value == AuthTokenDao.NEVER_EXPIRE ? Long.MAX_VALUE : value;
    }

    // ----------------------- 存取值 (类型转换)

    // ---- 取值

    /**
     * 取值
     *
     * @param key key
     * @return 值
     */
    public Object get(AuthSession authSession, String key) {
        return authSession.getDataMap().get(key);
    }

    /**
     * 取值 (指定默认值)
     *
     * @param <T>          默认值的类型
     * @param key          key
     * @param defaultValue 取不到值时返回的默认值
     * @return 值
     */
    public <T> T get(AuthSession authSession, String key, T defaultValue) {
        return getValueByDefaultValue(get(authSession, key), defaultValue);
    }

    /**
     * 取值 (如果值为null，则执行fun函数获取值)
     *
     * @param <T> 返回值的类型
     * @param key key
     * @param fun 值为null时执行的函数
     * @return 值
     */
    @SuppressWarnings("unchecked")
    public <T> T get(AuthSession authSession, String key, AuthRetFunction fun) {
        Object value = get(authSession, key);
        if (value == null) {
            value = fun.run();
            set(authSession, key, value);
        }
        return (T) value;
    }

    /**
     * 取值 (转String类型)
     *
     * @param key key
     * @return 值
     */
    public String getString(AuthSession authSession, String key) {
        Object value = get(authSession, key);
        if (value == null) {
            return null;
        }
        return String.valueOf(value);
    }

    /**
     * 取值 (转int类型)
     *
     * @param key key
     * @return 值
     */
    public int getInt(AuthSession authSession, String key) {
        return getValueByDefaultValue(get(authSession, key), 0);
    }

    /**
     * 取值 (转long类型)
     *
     * @param key key
     * @return 值
     */
    public long getLong(AuthSession authSession, String key) {
        return getValueByDefaultValue(get(authSession, key), 0L);
    }

    /**
     * 取值 (转double类型)
     *
     * @param key key
     * @return 值
     */
    public double getDouble(AuthSession authSession, String key) {
        return getValueByDefaultValue(get(authSession, key), 0.0);
    }

    /**
     * 取值 (转float类型)
     *
     * @param key key
     * @return 值
     */
    public float getFloat(AuthSession authSession, String key) {
        return getValueByDefaultValue(get(authSession, key), 0.0f);
    }

    /**
     * 取值 (指定转换类型)
     *
     * @param <T> 泛型
     * @param key key
     * @param cs  指定转换类型
     * @return 值
     */
    public <T> T getModel(AuthSession authSession, String key, Class<T> cs) {
        return FoxUtil.getValueByType(get(authSession, key), cs);
    }

    /**
     * 取值 (指定转换类型, 并指定值为Null时返回的默认值)
     *
     * @param <T>          泛型
     * @param key          key
     * @param cs           指定转换类型
     * @param defaultValue 值为Null时返回的默认值
     * @return 值
     */
    @SuppressWarnings("unchecked")
    public <T> T getModel(AuthSession authSession, String key, Class<T> cs, Object defaultValue) {
        Object value = get(authSession, key);
        if (valueIsNull(value)) {
            return (T) defaultValue;
        }
        return FoxUtil.getValueByType(value, cs);
    }

    /**
     * 返回当前Session的所有key
     *
     * @return 所有值的key列表
     */
    public Set<String> keys(AuthSession authSession) {
        return authSession.getDataMap().keySet();
    }

    // ---- 其他

    /**
     * 写值
     *
     * @param key   名称
     * @param value 值
     * @return 对象自身
     */
    public void set(AuthSession authSession, String key, Object value) {
        authSession.getDataMap().put(key, value);
        update(authSession);
    }

    /**
     * 写值(只有在此key原本无值的时候才会写入)
     *
     * @param key   名称
     * @param value 值
     * @return 对象自身
     */
    public void setDefaultValue(AuthSession authSession, String key, Object value) {
        if (has(authSession, key) == false) {
            authSession.getDataMap().put(key, value);
            update(authSession);
        }
    }

    /**
     * 是否含有某个key
     *
     * @param key has
     * @return 是否含有
     */
    public boolean has(AuthSession authSession, String key) {
        return !valueIsNull(get(authSession, key));
    }

    /**
     * 删值
     *
     * @param key 要删除的key
     * @return 对象自身
     */
    public void delete(AuthSession authSession, String key) {
        authSession.getDataMap().remove(key);
        update(authSession);
    }

    /**
     * 清空所有值
     */
    public void clear(AuthSession authSession) {
        authSession.getDataMap().clear();
        update(authSession);
    }

    /**
     * 写入数据集合 (不改变底层对象，只将此dataMap所有数据进行替换)
     *
     * @param dataMap 数据集合
     */
    public void refreshDataMap(AuthSession authSession, Map<String, Object> dataMap) {
        authSession.getDataMap().clear();
        authSession.getDataMap().putAll(dataMap);
        this.update(authSession);
    }

    // --------- 工具方法

    /**
     * 判断一个值是否为null
     *
     * @param value 指定值
     * @return 此value是否为null
     */
    public boolean valueIsNull(Object value) {
        return value == null || value.equals("");
    }

    /**
     * 根据默认值来获取值
     *
     * @param <T>          泛型
     * @param value        值
     * @param defaultValue 默认值
     * @return 转换后的值
     */
    @SuppressWarnings("unchecked")
    protected <T> T getValueByDefaultValue(Object value, T defaultValue) {

        // 如果 obj 为 null，则直接返回默认值
        if (valueIsNull(value)) {
            return (T) defaultValue;
        }

        // 开始转换
        Class<T> cs = (Class<T>) defaultValue.getClass();
        return FoxUtil.getValueByType(value, cs);
    }

}
